package judge

import (
	"JudgeSandBox/common"
	"JudgeSandBox/utils"
	"fmt"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"syscall"
)

type Judge interface {
	// NewWorkSpace 使用Overlay创建工作空间
	NewWorkSpace(request common.JudgeRequest) error
	// Compile 在工作空间内进行编译
	Compile(request common.JudgeRequest) error
	// Execute 执行
	Execute(request common.JudgeRequest) (*common.JudgeResponse, error)
	// Done 收尾工作
	Done(request common.JudgeRequest)
}

type Impl struct {
	Judge
}

type CompileError struct {
	error
	msg string
}

func (e CompileError) Error() string {
	return e.msg
}

func (j Impl) NewWorkSpace(request common.JudgeRequest) error {
	//创建目录 /root/judge/tmp/xxxxx
	tmpUuidPath := path.Join(common.TmpPath, request.Uuid)
	if exists, _ := utils.PathExists(tmpUuidPath); !exists {
		if err := os.Mkdir(tmpUuidPath, 0666); err != nil {
			return err
		}
	}

	// systemd 加入linux之后, mount namespace 就变成 shared by default, 所以你必须显示
	//声明你要这个新的mount namespace独立。
	err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, "")
	if err != nil {
		return err
	}

	///root/judge/tmp/xxxxx/mnt
	if err = os.MkdirAll(path.Join(tmpUuidPath, "mnt"), 0666); err != nil {
		utils.PrintToLog(err)
	}
	///root/judge/tmp/xxxxx/work
	if err = os.MkdirAll(path.Join(tmpUuidPath, "work"), 0666); err != nil {
		utils.PrintToLog(err)
	}
	///root/judge/tmp/xxxxx/upper
	if err = os.MkdirAll(path.Join(tmpUuidPath, "upper"), 0666); err != nil {
		utils.PrintToLog(err)
	}

	//overlayfs
	// /home/magua002/ubuntu ubuntu镜像，/home/magua002/lang java,go,python的安装目录
	if err = exec.Command("mount", "-t", "overlay", "overlay", "-o",
		"lowerdir=/home/magua002/ubuntu:/home/magua002/lang,"+ //lower dir
			fmt.Sprintf("upperdir=%s,", path.Join(tmpUuidPath, "upper"))+ //upper dir
			fmt.Sprintf("workdir=%s", path.Join(tmpUuidPath, "work")), //work dir
		path.Join(tmpUuidPath, "mnt")).Run(); err != nil { //target dir
		utils.PrintToLog(err)
	}

	//还是决定用chroot
	/*if err = PivotRoot(path.Join(tmpUuidPath, "mnt")); err != nil {
		utils.PrintToLog(err)
	}*/

	//mount proc 必须挂载proc，否则可能会出现elf找不到so的情况
	defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
	syscall.Mount("proc", path.Join(tmpUuidPath, "mnt", "proc"), "proc", uintptr(defaultMountFlags), "")

	return nil
}

func (i Impl) Done(request common.JudgeRequest) {
	tmpUuidPath := path.Join(common.TmpPath, request.Uuid)
	//卸载proc
	if err := exec.Command("umount", path.Join(tmpUuidPath, "mnt", "proc")).Run(); err != nil {
		utils.PrintToLog("umount proc", err)
	}
	//卸载mnt
	if err := exec.Command("umount", path.Join(tmpUuidPath, "mnt")).Run(); err != nil {
		utils.PrintToLog("umount mnt", err)
	}
	//删除/tmp/xxx
	if err := os.RemoveAll(path.Join(tmpUuidPath, "upper")); err != nil {
		utils.PrintToLog("removeAll", path.Join(tmpUuidPath, "upper"), err)
	}
	if err := os.RemoveAll(path.Join(tmpUuidPath, "work")); err != nil {
		utils.PrintToLog("removeAll", path.Join(tmpUuidPath, "work"), err)
	}
	if err := os.RemoveAll(path.Join(tmpUuidPath, "mnt")); err != nil {
		utils.PrintToLog("removeAll", path.Join(tmpUuidPath, "nmt"), err)
	}
	if err := os.RemoveAll(tmpUuidPath); err != nil {
		utils.PrintToLog("removeAll", tmpUuidPath, err)
	}
}

func GetJudgeImpl(lang string) Judge {
	if lang == "java" {
		return Java{Impl{}}
	} else if lang == "golang" {
		return Golang{Impl{}}
	} else if lang == "python" {
		return Python{Impl{}}
	}
	return nil
}

func PivotRoot(root string) error {
	/**
	  为了使当前root的老 root 和新 root 不在同一个文件系统下，我们把root重新mount了一次
	  bind mount是把相同的内容换了一个挂载点的挂载方法
	*/
	if err := syscall.Mount(root, root, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
		return fmt.Errorf("Mount rootfs to itself error: %v", err)
	}
	// 创建 rootfs/.pivot_root 存储 old_root
	pivotDir := filepath.Join(root, ".pivot_root")
	if err := os.Mkdir(pivotDir, 0777); err != nil {
		return err
	}
	// pivot_root 到新的rootfs, 现在老的 old_root 是挂载在rootfs/.pivot_root
	// 挂载点现在依然可以在mount命令中看到
	if err := syscall.PivotRoot(root, pivotDir); err != nil {
		return fmt.Errorf("pivot_root %v", err)
	}
	// 修改当前的工作目录到根目录
	if err := syscall.Chdir("/"); err != nil {
		return fmt.Errorf("chdir / %v", err)
	}

	pivotDir = filepath.Join("/", ".pivot_root")
	// umount rootfs/.pivot_root
	if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
		return fmt.Errorf("unmount pivot_root dir %v", err)
	}
	// 删除临时文件夹
	return os.Remove(pivotDir)
}
